/*
 * @Author: wuqingchun
 * @Date: 2024-05-08 15:17:55
 * @Description: 信号量使用
 * @LastEditors: wuqingchun
 * @LastEditTime: 2024-05-09 15:27:44
 */

#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#include <thread>

/**
 * @brief System V 信号量封装
 * 
 */
class IPCSysVSema
{
public:
    ~IPCSysVSema()
    {
        if (m_iSemID != -1)
        {
            // 删除信号量集（可选）  
            // if (semctl(m_iSemID, 0, IPC_RMID, 0) == -1) {  
            //     perror("semctl");
            // } 
        }
    }

    int32_t Init()
    {
        key_t key;  
        int semid;  
    
        // 假设key已经被正确设置  
        key = ftok(".", 'R');  
        if (key == -1)
        {
            perror("ftok");
            return -1;
        }

        int32_t iErr = __CreateSema(key);
        if (iErr < 0)
        {
            // 初始化失败
            return -1;
        }

        if (iErr == 1)
        {
            iErr = __GetSema(key);
        }

        return iErr;
    }


    int32_t Post()
    {
        if (m_iSemID == -1)
        {
            return -1;
        }

        return __SemOper(1);
    }

    int32_t Wait()
    {
        if (m_iSemID == -1)
        {
            return -1;
        }

        return __SemOper(-1);
    }
    
    void GetValue(int iSemID)
    {
        int iVal = semctl(iSemID, 0, GETVAL);
        if (iVal == -1)
        {
            perror("semctl"); 
            return;
        }

        printf("value: %d\n", iVal);
    }

private:
    int32_t __SemOper(short sOp)
    {
        struct sembuf sem_op = {};
         
        sem_op.sem_num = 0; // 信号量在信号量集中的索引  
        sem_op.sem_op = sOp; // PV操作，使信号量减1或加1
        sem_op.sem_flg = SEM_UNDO|IPC_NOWAIT; // 操作标志

        return semop(m_iSemID, &sem_op, 1);
    }

    /**
     * @brief 创建信号量
     * 
     * @param iKey      
     * @return int32_t 0 成功，-1 失败，1 需要重试
     */
    int32_t __CreateSema(key_t iKey)
    {
        // 独占创建
        int iSemID = semget(iKey, 1, IPC_CREAT | IPC_EXCL | 0666);
        if (iSemID == -1)
        {
            if (errno == EEXIST)
            {
                return 1;
            }
            
            perror("semget"); 

            return -1;
        }

        // 初始化信号量（假设初始值为1）  
        union semun {  
            int val;  
            struct semid_ds *buf;  
            unsigned short *array;  
            struct seminfo *__buf;  
        } sem_union;

        sem_union.val = 1;

        GetValue(iSemID);

        if (semctl(iSemID, 0, SETVAL, sem_union) == -1) 
        {  
            __DeleteSema(iSemID);
            perror("semctl"); 
            
            return -1;
        }

        m_iSemID = iSemID;

        return 0;
    }

    /**
     * @brief 获取信号量
     * 
     * @param iKey 
     * @return int32_t 
     */
    int32_t __GetSema(key_t iKey)
    {
        int iSemID = semget(iKey, 1, IPC_CREAT | 0666); 
        if (iSemID == -1)
        {
            perror("semget"); 
            return -1;
        }

        GetValue(iSemID);

        m_iSemID = iSemID;
        return 0;
    }

    /**
     * @brief 删除信号量
     * 
     * @param iSemID 
     */
    void __DeleteSema(int iSemID)
    {
        (void)semctl(iSemID, 0, IPC_RMID, 0);
    }
    
private:
    int m_iSemID = -1;
};


int main(int argc, char** argv)
{
    IPCSysVSema objSema;

    if (objSema.Init() != 0)
    {
        return 0;
    }

    if (objSema.Wait() != 0)
    {
        printf("wait: %s, %d\n", strerror(errno), errno);
        return 0;
    }

    while (1)
    {
        printf("do something\n");
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    return 0;
}